package engine;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * All children of this class that are still strongly referenced and have not been removed with
 * <code>dontPersist()</code> will be saved when calling <code>Persistent.save()</code> and loaded
 * when calling <code>Persistent.load()</code>.
 * 
 * @author Kyle_Solo
 */
public abstract class Persistent {
	/**
	 * This list holds all the persistent objects in such a way that they may still be garbage
	 * collected.
	 */
	private static final List<WeakReference<Persistent>> list = new ArrayList<WeakReference<Persistent>>();

	/**
	 * Loads all the persistent objects in the directory
	 * 
	 * @param directory
	 *            a directory where persistent objects have been saved
	 * @return success
	 */
	@SuppressWarnings("unchecked")
	public static final boolean load(String directory) {
		File dir = new File(directory);
		File[] files = dir.listFiles();
		if (files == null) {
			System.err.println("Error: Could not find load directory.");
			return false;
		} else {
			// load each creature
			for (File file : files) {
				if (file.exists() && file.isFile() && file.canRead()) {
					try {
						Scanner persistInput = new Scanner(file);
						if (!persistInput.hasNextLine()) {
							throw new Exception("Invalid persistent file.");
						}
						String className = persistInput.nextLine();
						Class<Persistent> readClass = (Class<Persistent>) Class.forName(className);
						Persistent c = readClass.newInstance();
						if (!c.load(persistInput)) {
							throw new Exception("Invalid persistent file.");
						}
					} catch (ClassNotFoundException e) {
						System.out.println(file.getName()
								+ " was not recognized as a persistent class");
					} catch (ClassCastException e) {
						System.out.println(file.getName()
								+ " was not recognized as a persistent class");
					} catch (Exception e) {
						System.err.println("Unexpected error: Unable to load persistent class: "
								+ e.getMessage());
						e.printStackTrace();
					}

				}
			}
			return true;
		}
	}

	/**
	 * Saves all the persistent objects to a directory
	 * 
	 * @param directory
	 *            a directory to save persistent objects
	 * @return success
	 */
	public static final boolean save(String directory) {
		/**
		 * Ideally, this will force a full garbage collection, which should clean out all the
		 * non-strongly referenced objects from our list. In a perfect world where this actually
		 * happened all the time on all Operating Systems, we wouldn't need dontPersist.
		 * <p>
		 * As it is, this is more of a suggestion to the garbage collector than a command. I don't
		 * know why they even provide it.
		 */
		System.gc();

		for (int i = 0; i < Persistent.list.size(); i++) {
			Persistent p = Persistent.list.get(i).get();
			if (p != null) {
				File file = new File(directory + p.getSaveFilename());
				try {
					PrintStream out = new PrintStream(file);
					out.print(p.getClass().getCanonicalName() + "\r\n");
					p.save(out);
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
		return true;
	}

	/**
	 * Constructs a new Persistent object. Every persistent object must have a default constructor.
	 */
	public Persistent() {}

	/**
	 * Weakly adds the Object to the reference list. This is an instance initializer, in case you
	 * were wondering.
	 */
	{
		Persistent.list.add(new WeakReference<Persistent>(this));
	}

	/**
	 * Ensures that this object won't be saved. This method must be called on every Persistent
	 * object that shouldn't be saved.
	 */
	public final void dontPersist() {
		for (int i = 0; i < Persistent.list.size(); i++) {
			if (Persistent.list.get(i).get() == this) {
				Persistent.list.remove(i);
				return;
			}
		}
	}

	/**
	 * @return a unique save filename for this object
	 */
	public abstract String getSaveFilename();

	/**
	 * Loads the object from a scanner. This method uses the data previously saved by save(), and is
	 * called automatically from Persistent.load.
	 * 
	 * @param data
	 *            the scanner to load from
	 * @return success
	 */
	public abstract boolean load(Scanner data);

	/**
	 * Saves the object's data to the given PrintStream. This is called automatically from
	 * Persistent.save.
	 * 
	 * @param data
	 *            the PrintStream to save the object to
	 * @return success
	 */
	public abstract boolean save(PrintStream data);
}